/*
 * Copyright 2000 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "WtAppTable.h"
#include "WtTableCmds.h"
#include "WtTableUtil.h"
#include "WtUtil.h"
#include "WtMtTable.h"
#include "WtTable.h"

WtAppTableSet wtAppTables;

void WtInitAppTableSet(WtAppTableSet *set)
{
  Tcl_InitObjHashTable(&set->tables);
  set->lock = NULL;
  set->seqId = 0;
}

/* Open an app table */

Tcl_Obj *WtOpenAppTable(WtAppTableSet *set, Tcl_Obj *id,
    Tcl_Interp *interp, int *created)
{
  int ok = 1;
  Tcl_HashEntry *ent;
  Tcl_Obj *tbl = NULL;
  WtAppTable *at;

  Tcl_MutexLock(&set->lock);

  *created = 0;
  ent = Tcl_CreateHashEntry(&set->tables, (char *)id,
    created);

  if (!ent) {
    ok = 0;
    Tcl_MutexUnlock(&set->lock);
  } else {
    if (!*created) {
      tbl = (Tcl_Obj *)Tcl_GetHashValue(ent);
    } else {
      if (tbl = WtNewAppTableObj()) {
        Tcl_IncrRefCount(tbl);
        Tcl_SetHashValue(ent, tbl);
      } else {
        Tcl_DeleteHashEntry(ent);
        *created = 0;
        ok = 0;
      }
    }

    if (ok) {
      Tcl_IncrRefCount(tbl);
    }

    Tcl_MutexUnlock(&set->lock);

    if (ok) {
      at = WtAppTableRep(tbl);
      at->accessTime = time(NULL);
    }
  }

  return tbl;
}

/* Close an app table */

int WtCloseAppTable(Tcl_Obj *tbl)
{
  Tcl_DecrRefCount(tbl);
  return 1;
}

/* Delete an app table */

int WtDeleteAppTable(WtAppTableSet *set, Tcl_Obj *id, Tcl_Interp *interp)
{
  int ok = 1;
  Tcl_Obj *tbl;
  Tcl_HashEntry *ent;

  Tcl_MutexLock(&set->lock);

  ent = Tcl_FindHashEntry(&set->tables, (char *)id);

  if (ent) {
    tbl = (Tcl_Obj *)Tcl_GetHashValue(ent);
    if (tbl) {
      Tcl_DecrRefCount(tbl);
    }
    Tcl_DeleteHashEntry(ent);
  }

  Tcl_MutexUnlock(&set->lock);

  return ok;
}

/* Helper to manage table objects */

WtTableCmdStatus WtAppTableCmdHelper(ClientData clientData,
    Tcl_Interp *interp, int objc, Tcl_Obj *const objv[],
    int *tclRet, int readOnly, int appendUsage)
{
  int status = WT_TBL_CMD_DONE, ret = TCL_ERROR, error = 0, created = 0;
  WtContext *w;
  char *subCmd, idStr[128];
  Tcl_Obj *tbl = NULL, *mt, *obj, *key, *val;

  w = WtGetAssocContext(interp);

  if (objc < 2) {
    if (appendUsage) {
      WtAppTableCmdUsageError(interp, objv[0], readOnly);
    }

    *tclRet = TCL_ERROR;

    return WT_TBL_CMD_MISSING;
  }

  subCmd = WtToString(objv[1]);

  if (!strcmp(subCmd, "open")) {

    /* open command */

    if (objc == 2 || objc == 3) {
      if (objc == 2) {
        Tcl_MutexLock(&wtAppTables.lock);
        sprintf(idStr, "appTable-%lu", wtAppTables.seqId++);
        Tcl_MutexUnlock(&wtAppTables.lock);
        obj = WtNewString(idStr);
        Tcl_IncrRefCount(obj);
      } else {
        obj = objv[2];
      }
      if (tbl = WtOpenAppTable(&wtAppTables, obj, interp, &created)) {
        Tcl_SetObjResult(interp, WtNewString(Tcl_GetString(obj)));
        ret = TCL_OK;
      }
      if (objc == 2) {
        Tcl_DecrRefCount(obj);
      }
    } else {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " ?id?",
        NULL);
    }

  } else if (!strcmp(subCmd, "count")) {

    /* count command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      Tcl_SetObjResult(interp, Tcl_NewIntObj(WtMtTableSize(mt)));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "has")) {

    /* has command */

    if (objc != 4) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " table key", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
        WtMtTableHas(mt, objv[3])));
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "keys")) {

    /* keys command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      if (WtMtTableKeys(mt, &obj, interp)) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "values")) {

    /* values command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      if (WtMtTableValues(mt, &obj, interp)) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "get")) {

    /* get command */

    if (objc < 4 || objc > 5) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ",
        WtToString(objv[1]), " table key ?defaultValue?", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      obj = WtMtTableGet(mt, objv[3]);
      if (obj) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      } else if (objc > 4) {
        Tcl_SetObjResult(interp, objv[4]);
        ret = TCL_OK;
      } else {
        Tcl_AppendResult(interp, "No entry with key \"",
          WtToString(objv[3]), "\" found.", NULL);
      }
    }

  } else if (!strcmp(subCmd, "set") && !readOnly) {

    /* set command */

    if (objc != 5) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key value", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableSet(mt, objv[3], objv[4]);
      Tcl_SetObjResult(interp, objv[4]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "setDefault") && !readOnly) {

    /* setDefault command */

    if (objc != 5) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " ",
        " table key value", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      if (obj = WtMtTableGet(mt, objv[3])) {
        Tcl_SetObjResult(interp, obj);
      } else {
        WtMtTableSet(mt, objv[3], objv[4]);
        Tcl_SetObjResult(interp, objv[4]);
      }
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "lappend") && !readOnly) {

    /* lappend command */

    if (objc != 5) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table key value", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      key = Tcl_DuplicateObj(objv[3]);
      Tcl_IncrRefCount(key);
      val = Tcl_DuplicateObj(objv[4]);
      Tcl_IncrRefCount(val);
      WtMtTableLock(mt);

      if (WtTableAppendToList(WtMtTableRep(mt)->tbl, key, val, &obj,
          interp)) {
        Tcl_SetObjResult(interp, Tcl_DuplicateObj(obj));
        ret = TCL_OK;
      }

      WtMtTableUnlock(mt);
      Tcl_DecrRefCount(key);
      Tcl_DecrRefCount(val);
    }

  } else if (!strcmp(subCmd, "remove") && !readOnly) {

    /* remove command */

    if (objc != 4) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " table key", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableRemove(mt, objv[3]);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "removeList") && !readOnly) {

    /* removeList command */

    if (objc != 4) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " table list", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      if (WtMtTableRemoveList(mt, objv[3], interp)) {
        ret = TCL_OK;
      }
    }

  } else if (!strcmp(subCmd, "items")) {

    /* items command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableLock(mt);
      Tcl_SetObjResult(interp, Tcl_DuplicateObj(WtMtTableRep(mt)->tbl));
      WtMtTableUnlock(mt);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "list")) {

    /* list command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableLock(mt);
      if (obj = WtTableToList(WtMtTableRep(mt)->tbl, 0, interp)) {
        Tcl_SetObjResult(interp, Tcl_DuplicateObj(obj));
        ret = TCL_OK;
      }
      WtMtTableUnlock(mt);
    }

  } else if (!strcmp(subCmd, "array")) {

    /* array command */

    if (objc != 4) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " table arrayName", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableLock(mt);
      if (WtTableToArray(interp, WtMtTableRep(mt)->tbl, objv[3], 0)) {
        ret = TCL_OK;
      }
      WtMtTableUnlock(mt);
    }

  } else if (!strcmp(subCmd, "copy")) {

    /* copy command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableLock(mt);
      if (obj = Tcl_DuplicateObj(WtMtTableRep(mt)->tbl)) {
        Tcl_SetObjResult(interp, obj);
        ret = TCL_OK;
      }
      WtMtTableUnlock(mt);
    }

  } else if (!strcmp(subCmd, "clear") && !readOnly) {

    /* clear command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableClear(mt);
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "delete") && !readOnly) {

    /* delete command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (WtDeleteAppTable(&wtAppTables, objv[2], interp)) {
      ret = TCL_OK;
    }

  } else if (!strcmp(subCmd, "importVars")) {

    /* importVars command */

    if (objc != 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]), " tableId", NULL);
    } else if (mt = WtAppTableCmdGetStorage(objv[2], &tbl, interp)) {
      WtMtTableLock(mt);
      if (WtImportTableVars(tbl, interp)) {
        ret = TCL_OK;
      }
      WtMtTableUnlock(mt);
    }

  } else {

    /* unknown command */

    status = WT_TBL_CMD_NOT_FOUND;

    if (appendUsage) {
      WtAppTableCmdUsageError(interp, objv[0], readOnly);
    }

  }

  if (tbl && !WtCloseAppTable(tbl)) {
    ret = TCL_ERROR;
  }

  *tclRet = ret;

  return status;
}

void WtAppTableCmdUsageError(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *interpRes = Tcl_GetObjResult(interp);
  Tcl_AppendResult(interp, wtBadUsagePrefix2, NULL);
  WtAppTableCmdAppendUsage(interp, cmd, readOnly);
}

void WtAppTableCmdAppendUsage(Tcl_Interp *interp, Tcl_Obj *cmd, int readOnly)
{
  Tcl_Obj *res = Tcl_GetObjResult(interp);
  char *cmdStr = WtToString(cmd);

  if (readOnly) {
    Tcl_AppendStringsToObj(res,
      cmdStr, " open ?tableId?\n",
      cmdStr, " count tableId\n",
      cmdStr, " has tableId key\n",
      cmdStr, " keys tableId\n",
      cmdStr, " values tableId\n",
      cmdStr, " get tableId key ?defaultValue?\n",
      cmdStr, " items tableId\n",
      cmdStr, " list tableId\n",
      cmdStr, " array tableId arrayName\n",
      cmdStr, " copy tableId\n",
      cmdStr, " importVars tableId\n",
      NULL);
  } else {
    Tcl_AppendStringsToObj(res,
      cmdStr, " open ?tableId?\n",
      cmdStr, " count tableId\n",
      cmdStr, " has tableId key\n",
      cmdStr, " keys tableId\n",
      cmdStr, " values tableId\n",
      cmdStr, " get tableId key ?defaultValue?\n",
      cmdStr, " set tableId key value\n",
      cmdStr, " setDefault tableId key value\n",
      cmdStr, " append tableId key value\n",
      cmdStr, " add tableId key value\n",
      cmdStr, " remove tableId key\n",
      cmdStr, " removeList tableId keys\n",
      cmdStr, " items tableId\n",
      cmdStr, " list tableId\n",
      cmdStr, " array tableId arrayName\n",
      cmdStr, " copy tableId\n",
      cmdStr, " importVars tableId\n",
      cmdStr, " clear tableId\n",
      cmdStr, " delete tableId\n",
      NULL);
  }
}

int WtAppTableCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret;
  WtAppTableCmdHelper(clientData, interp, objc, objv, &ret, 0, 1);
  return ret;
}

/* Get the app table referenced by the command */

Tcl_Obj *WtAppTableCmdGetTarget(Tcl_Obj *id, Tcl_Interp *interp)
{
  int created;
  return WtOpenAppTable(&wtAppTables, id, interp, &created);
}

/* Get an app table's storage object */

Tcl_Obj *WtAppTableCmdGetStorage(Tcl_Obj *id, Tcl_Obj **tbl,
    Tcl_Interp *interp)
{
  WtAppTable *at;
  Tcl_Obj *mt = NULL;
  int created;

  *tbl = WtOpenAppTable(&wtAppTables, id, interp, &created);

  if (*tbl) {
    at = WtAppTableRep(*tbl);
    mt = at->sharedTable;
  }

  return mt;
}

/* App table object */

/* Create */

Tcl_Obj *WtNewAppTableObj()
{
  Tcl_Obj *obj;

  WtAppTable *at = (WtAppTable *)ckalloc(sizeof(WtAppTable));
  at->sharedTable = WtNewMtTableObj();

  obj = Tcl_NewObj();
  obj->internalRep.twoPtrValue.ptr1 = at;
  obj->internalRep.twoPtrValue.ptr2 = NULL;
  obj->typePtr = &WtAppTableType;
  Tcl_InvalidateStringRep(obj);
  return obj;
}

/* Copy */

static void WtAppTableDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtAppTable *srcRep = WtAppTableRep(src);
  WtAppTable *dstRep = (WtAppTable *)ckalloc(
    sizeof(WtAppTable));

  if (srcRep->sharedTable) {
    dstRep->sharedTable = Tcl_DuplicateObj(dstRep->sharedTable);
    Tcl_IncrRefCount(dstRep->sharedTable);
  } else {
    dstRep->sharedTable = NULL;
  }

  dst->internalRep.twoPtrValue.ptr1 = dstRep;
  dst->internalRep.twoPtrValue.ptr2 = NULL;
  dst->typePtr = &WtAppTableType;
  Tcl_InvalidateStringRep(dst);
}

/* Update the string rep */

static void WtAppTableUpdateString(Tcl_Obj *obj)
{
  WtUpdateProtectedObjString(obj);
}

/* Convert */

static int WtAppTableSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetProtectedObjFromAny(interp, obj, &WtAppTableType);
}

/* Free */

static void WtAppTableFree(Tcl_Obj *obj)
{
  WtAppTable *at = WtAppTableRep(obj);
  if (at->sharedTable) {
    Tcl_DecrRefCount(at->sharedTable);
    at->sharedTable = NULL;
  }
  ckfree((char *)(at));
}

WtAppTable *WtAppTableRep(Tcl_Obj *obj)
{
  return (WtAppTable *)obj->internalRep.twoPtrValue.ptr1;
}

/* Process session type */

struct Tcl_ObjType WtAppTableType =
{
  "appTable",
  WtAppTableFree,
  WtAppTableDup,
  WtAppTableUpdateString,
  WtAppTableSetFromAny
};

void WtInitAppTableCommands(Tcl_Interp *interp)
{
  Tcl_CreateObjCommand(interp, "appTable", WtAppTableCmd, NULL, NULL);
}
